using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;
using System.Globalization;

namespace Serialization
{
    public class ObjectReader
    {
#if PocketPC
        private static MethodInfo TypeCreateInstance;
#endif
        
        private Dictionary<long, object> _objectMap = new Dictionary<long, object>();
        private ISurrogateSelector _selector;

        public ObjectReader()
        {
        }

        public ObjectReader(ISurrogateSelector selector)
        {
            _selector = selector;
        }

#if PocketPC
        static ObjectReader()
        {
            TypeCreateInstance = typeof(Type).GetMethod("CreateInstance", BindingFlags.Instance | BindingFlags.NonPublic);
        }
#endif
        public object Read(Stream s)
        {
            BinaryReader reader = new BinaryReader(s);
            return Read(reader);
        }

        private object Read(BinaryReader reader)
        {
            try
            {
                TypeInfo typeInfo = (TypeInfo)reader.ReadByte();
                int typeCode = (int) TypeCode.Object;
                string typeName = null;
                long objId = 0;
                Type objType = null;
                object ret = null;

                if (TypeUtil.IsObjectNull(typeInfo))
                {
                    return null;
                }
                if(TypeUtil.IsPrimitiveType(typeInfo))
                {
                    typeCode = reader.ReadInt32();
                    objType = TypeUtil.TypeFromTypeCode(typeCode);
                    typeName = GetTypeName(typeInfo, objType);
                }
                else
                {
                    typeCode = (int) TypeCode.Object;
                    if (TypeUtil.IsReferenceType(typeInfo) && !TypeUtil.IsArray(typeInfo))
                    {
                        objId = reader.ReadInt64();
                        if (objId == 0)
                        {
                            return null;
                        }
                        if (_objectMap.ContainsKey(objId))
                        {
                            return _objectMap[objId];
                        }
                    }
                    typeName = reader.ReadString();
                    objType = Type.GetType(typeName);
                }
                
                if (TypeUtil.IsArray(typeInfo))
                {
                    int rank = reader.ReadInt32();
                    int[] lengths = new int[rank];
                    int[] indices = new int[rank];
                    for (int i = 0; i < rank; i++)
                    {
                        lengths[i] = reader.ReadInt32();
                    }
                    
                    Array a = Array.CreateInstance(objType, lengths);
                    ReadArrayElements(reader, objType, a, 0, lengths, indices);
                    ret = a;
                }
                else if (TypeUtil.IsPrimitiveType(typeInfo))
                {
                    ret = ReadPrimitiveType(reader, typeCode);
                }
                else
                {
                    ret = GetUninitializedObject(objType);
                    ISurrogateSelector selectedSelector = null;
                    ISerializationSurrogate surrogate = TypeUtil.FindSurrogate(objType, _selector, out selectedSelector);
                    if (surrogate != null)
                    {
                        SerializationInfo info = ReadSerializationInfo(reader, objType);
                        surrogate.SetObjectData(ret, info, selectedSelector);
                    }
                    else
                    {
                        Stack<Type> typeStack = TypeUtil.GetTypeStack(objType);
                        foreach (Type t in typeStack)
                        {
                            SerializationInfo info = ReadSerializationInfo(reader, t);
                            if (typeof(ISerializable).IsAssignableFrom(t))
                            {
                                ConstructorInfo ctor = t.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(SerializationInfo) }, null);
                                ctor.Invoke(ret, new object[] { info });
                            }
                            else
                            {
                                SetObjectData(ret, info);
                            }
                        }
                    }
                    if (TypeUtil.IsReferenceType(typeInfo))
                    {
                        _objectMap.Add(objId, ret);
                    }
                }                
                return ret;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private string GetTypeName(TypeInfo typeInfo, Type objType)
        {
            string asmName = objType.Module.Assembly.GetName().Name;
            return objType.FullName + ", " + asmName;
        }

        private SerializationInfo ReadSerializationInfo(BinaryReader reader, Type t)
        {
            SerializationInfo info = new SerializationInfo(t);
            int count = reader.ReadInt32();
            for (int i = 0; i < count; i++)
            {
                string name = reader.ReadString();
                object value = Read(reader);
                info.AddValue(name, value, value == null ? null : value.GetType());
            }
            return info;
        }

        private void SetObjectData(object ret, SerializationInfo info)
        {
            Type objType = info.Type;
            FieldInfo[] fields = objType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
            foreach (FieldInfo fi in fields)
            {
                if ((fi.Attributes & FieldAttributes.NotSerialized) != 0)
                {
                    continue;
                }
                object value = info.GetValue(fi.Name);
                fi.SetValue(ret, value);
            }
        }

        private object ReadPrimitiveType(BinaryReader reader, int typeCode)
        {
            switch((TypeCode) typeCode)
            {
                case TypeCode.Byte:
                    return reader.ReadByte();
                case TypeCode.SByte:
                    return reader.ReadSByte();
                case TypeCode.Char:
                    return reader.ReadChar();
                case TypeCode.Boolean:
                    return reader.ReadBoolean();
                case TypeCode.Int16:
                    return reader.ReadInt16();
                case TypeCode.UInt16:
                    return reader.ReadUInt16();
                case TypeCode.Int32:
                    return reader.ReadInt32();
                case TypeCode.UInt32:
                    return reader.ReadUInt32();
                case TypeCode.Int64:
                    return reader.ReadInt64();
                case TypeCode.UInt64:
                    return reader.ReadUInt64();
                case TypeCode.Single:
                    return reader.ReadSingle();
                case TypeCode.Double:
                    return reader.ReadDouble();
                case TypeCode.String:
                    return reader.ReadString();
                case TypeCode.Decimal:
                    return decimal.Parse(reader.ReadString());
                case TypeCode.DateTime:
                    return new DateTime(reader.ReadInt64());
                case (TypeCode) TypeUtil.TypeCodeTimeSpan:
                    return new TimeSpan(reader.ReadInt64());
                case (TypeCode) TypeUtil.TypeCodeIntPtr:
                    return new IntPtr(reader.ReadInt64());
                case (TypeCode) TypeUtil.TypeCodeGuid:
                    return new Guid(reader.ReadBytes(16));
                default:
                    throw new Exception("Unknown primitive type code " + typeCode);
            }
        }

        private void ReadArrayElements(BinaryReader reader, Type elemType, Array a, int dimension, int[] lengths, int[] indices)
        {
            int length = lengths[dimension];
            for (int i = 0; i < length; i++)
            {
                indices[dimension] = i;
                if (dimension == a.Rank - 1)
                {
                    object value = Read(reader);
                    a.SetValue(value, indices);
                }
                else
                {
                    ReadArrayElements(reader, elemType, a, dimension + 1, lengths, indices);
                }
            }
        }

        private static object GetUninitializedObject(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }
            if (type.IsValueType || (type.IsArray && type.GetElementType().IsValueType))
            {
                return Activator.CreateInstance(type);
            }
            if (type.IsAbstract || type.IsInterface)
            {
                throw new MemberAccessException();
            }
            

            
#if PocketPC
                return TypeCreateInstance.Invoke(type, null);
#else
                return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(type);
#endif
        }
    }
}
